/*
 * RestHttpHandler.cpp
 *
 *  Created on: 2018年2月9日
 *      Author: terry
 */

#include "BasicType.h"
#include "RestHttpHandler.h"
#include "HttpUtil.h"
#include "TStringUtil.h"
#include "frozen.h"
#include "CHttpTokenAuth.h"

#include "mongoose.h"
#include "ErrorText.h"


RestHttpHandler::RestHttpHandler():
	m_auth(new CHttpTokenAuth())
{
}

RestHttpHandler::~RestHttpHandler()
{
    delete m_auth;
}

int RestHttpHandler::handle(HttpRequest& request, HttpResult& result)
{
	result.mime = "application/json";

    int ret = handleJsonApi(request, result);
    if (ret >= 0)
    {

    }
    return ret;
}

void RestHttpHandler::setUserStore(HttpUserStorePtr store)
{
    if (m_auth)
    {
        delete m_auth;
        m_auth = nullptr;
    }

    CHttpTokenAuth* auth = new CHttpTokenAuth();
    auth->setUserStore(store);

	m_auth = auth;
}

HttpTokenAuth* RestHttpHandler::getAuth()
{
	return m_auth;
}

bool RestHttpHandler::putToken(const std::string& token, const std::string& username, int expiration)
{
	return m_auth->putToken(token, username, expiration);
}

void RestHttpHandler::setup(const std::string& url, HttpProcessor proc)
{
	HttpProcessorMap& procMap = m_procTable[url];
	ProcessorEntry entry(url, "", proc);
	procMap[entry.method] = entry;
}

void RestHttpHandler::setup(const std::string& url, const std::string& method, HttpProcessor proc)
{
	HttpProcessorMap& procMap = m_procTable[url];
	ProcessorEntry entry(url, method, proc);
	procMap[entry.method] = entry;
}

void RestHttpHandler::setup(const std::string& url, const char* method, HttpProcessor proc)
{
	std::string m(method);
	setup(url, m, proc);
}

void RestHttpHandler::setup(const std::string& url, bool acquireAuth, HttpProcessor proc)
{
	HttpProcessorMap& procMap = m_procTable[url];
	ProcessorEntry entry(url, "", proc);
	entry.auth = acquireAuth;
	procMap[entry.method] = entry;
}

void RestHttpHandler::setup(const std::string& url, const std::string& method, bool acquireAuth, HttpProcessor proc)
{
	HttpProcessorMap& procMap = m_procTable[url];
	ProcessorEntry entry(url, method, proc);
	entry.auth = acquireAuth;
	procMap[entry.method] = entry;
}

void RestHttpHandler::setup(const std::string& url, const char* method, bool acquireAuth, HttpProcessor proc)
{
	std::string m(method);
	setup(url, m, acquireAuth, proc);
}

bool RestHttpHandler::getToken(HttpRequest& request, std::string& token)
{
	if (request.getHeader("Token", token))
	{
		return true;
	}

	if (request.getHeader("token", token))
	{
	    return true;
	}

	if (request.getParam("token", token))
	{
		return true;
	}

	std::string auth;
	if (request.getHeader("Authorization", auth))
	{
	    std::string head;
	    comn::StringUtil::split(auth, ' ', head, token);
		if (head.size() > 0 && token.empty())
		{
			token = head;
		}
	}

	return !token.empty();
}

bool RestHttpHandler::getUser(HttpRequest& request, std::string& username)
{
    std::string token;
    getToken(request, token);
	return m_auth->check(token, username);
}

bool RestHttpHandler::touch(HttpRequest& request, HttpResult& result)
{
	std::string token;
	getToken(request, token);
	if (!getToken(request, token))
	{
		return true;
	}

	return m_auth->touch(token);
}

bool RestHttpHandler::checkExpired(HttpRequest& request, HttpResult& result)
{
	std::string token;
	getToken(request, token);
	if (!getToken(request, token))
	{
		return true;
	}

	std::string username;
	if (!m_auth->check(token, username))
	{
		setError(401, "Expired", result);
		return false;
	}

	return true;
}

bool RestHttpHandler::checkAuth(HttpRequest& request, HttpResult& result)
{
    std::string token;
    getToken(request, token);
	if (!getToken(request, token))
	{
		setError(401, "need auth", result);
		return false;
	}

	std::string username;
	if (!m_auth->check(token, username))
	{
		setError(401, "Unauthorized", result);
		return false;
	}

	return true;
}

int RestHttpHandler::setError(int code, const std::string& message, HttpResult& result)
{
	int ret = setError(code, message, result.content);
	return ret;
}

int RestHttpHandler::setError(int code, HttpResult& result)
{
	std::string message = getCode(code, "");
	if (message.empty())
	{
	    ErrorText::get(code, message);
		if (message.empty())
		{
			message = comn::StringUtil::format("error code %d", code);
		}
	}
	int ret = setError(code, message, result.content);

	result.code = (code == 0) ? 200 : 400;
	return ret;
}

int RestHttpHandler::setError(int code, const std::string& message, std::string& jsonText)
{
	jsonText = makeResult(code, message);
	return code;
}

std::string RestHttpHandler::makeResult(int code, const std::string& message)
{
	return comn::StringUtil::format("{\"code\":%d, \"message\": \"%s\"}", code, message.c_str());
}

int RestHttpHandler::handleJsonApi(HttpRequest& request, HttpResult& result)
{
	ProcessorEntry entry;
	if (!findProcessor(request, entry))
	{
		return 404;
	}

	if (entry.auth)
	{
		if (!checkAuth(request, result))
		{
			return 401;
		}
	}
	else
	{
		touch(request, result);
	}

	try
	{
    	return entry.processor(request, result);
	}
	catch (std::exception& ex)
	{
		return setError(EFAULT, ex.what(), result);
	}
}

bool RestHttpHandler::findProcessor(HttpRequest& request, ProcessorEntry& entry)
{
	ProcessorTable::const_iterator it = findEntry(request, request.urlParams);
	if (it == m_procTable.end())
	{
		return false;
	}

	const HttpProcessorMap& procMap = it->second;
	std::string method = request.getMethod();
	HttpProcessorMap::const_iterator pos = procMap.find(method);
	if (pos == procMap.end())
	{
		pos = procMap.find("");
	}

	if (pos == procMap.end())
	{
		return false;
	}

	entry = pos->second;
	return true;
}

static size_t getSubString(const char* str, size_t len, size_t idx, char sp, std::string& value)
{
	size_t i = idx;
	for (; i < len; i ++)
	{
		if (str[i] == sp)
		{
			break;
		}

		value += str[i];
	}
	return i;
}

bool RestHttpHandler::matchEntry(HttpRequest& request, const std::string& pattern, StringMap& params)
{
    bool matched = false;

    StringMap curParams;

	const char SEPERATOR = '/';
	const char COLON = ':';

	size_t i = 0;
	size_t j = 0;

	std::string url = request.getUrl();
	while (i < pattern.size() && j < url.length())
	{
		std::string name;
		std::string value;

		if (pattern[i] == COLON)
		{
			i = getSubString(pattern.c_str(), pattern.size(), i + 1, SEPERATOR, name);
			j = getSubString(url.c_str(), url.length(), j, SEPERATOR, value);

            curParams[name] = value;
		}
		else if (pattern[i] == url[j])
		{
			i ++;
			j ++;
		}
		else
		{
			break;
		}
	}

    matched = (i >= pattern.size()) && (j >= url.length());

    if (matched)
    {
        params = curParams;
    }

    return matched;
}

RestHttpHandler::ProcessorTable::const_iterator RestHttpHandler::findEntry(HttpRequest& request, StringMap& params)
{
	ProcessorTable::const_iterator it = m_procTable.begin();
	for (; it != m_procTable.end(); ++ it)
	{
		if (matchEntry(request, it->first, params))
		{
			return it;
		}
	}
	return m_procTable.end();
}

int RestHttpHandler::login(HttpRequest& request, HttpResult& result, std::string& username, std::string& token)
{
    size_t len = 0;
    const char* body = request.getBody(len);

    mg_str bodyStr = mg_mk_str_n(body, len);

	std::string password;
    if (!request.getParam("username", username))
	{
        HttpUtil::getVar(&bodyStr, "username", username);
	}

    if (!request.getParam("password", password))
	{
		HttpUtil::getVar(&bodyStr, "password", password);
	}

    if (password.empty())
	{
		char* user = NULL;
		char* pass = NULL;
		int ret = json_scanf(body, len, "{username: %Q, password: %Q}", &user, &pass);
		if (ret < 2)
		{
			delete user;
			delete pass;

			ret = json_scanf(body, len, "{userName: %Q, password: %Q}", &user, &pass);
		}

		if (user)
		{
			username = user;
			delete user;
		}
		if (pass)
		{
			password = pass;
			delete pass;
		}
	}

	if (username.empty())
	{
		setError(401, "need username", result);
		return 401;
	}

	if (!m_auth->login(username, password, token))
	{
		/// 错误的用户名或者密码.
		setError(401, "\xE9\x94\x99\xE8\xAF\xAF\xE7\x9A\x84\xE7\x94\xA8\xE6\x88\xB7\xE5\x90\x8D\xE6\x88\x96\xE8\x80\x85\xE5\xAF\x86\xE7\xA0\x81", result);
		return 401;
	}

	result.content = comn::StringUtil::format("{\"username\":\"%s\", \"token\": \"%s\"}", username.c_str(), token.c_str());

	return 0;
}

int RestHttpHandler::logout(HttpRequest& request, HttpResult& result)
{
	std::string token;
	if (!getToken(request, token))
	{
		setError(401, "Unauthorized", result);
		return 401;
	}

	setError(0, "", result);

	m_auth->logout(token);
	return 0;
}

bool RestHttpHandler::loadCode(const std::string& filepath)
{
	return m_codeDict.load(filepath, "");
}

std::string RestHttpHandler::getCode(int code, const char* defValue)
{
	return m_codeDict.get(code, defValue);
}

bool RestHttpHandler::checkAuth(HttpRequest& request)
{
	std::string token;
	getToken(request, token);
	if (!getToken(request, token))
	{
		return false;
	}

	std::string username;
	if (!m_auth->check(token, username))
	{
		return false;
	}

	return true;
}

int RestHttpHandler::handleAuth(HttpRequest& request, HttpResult& result)
{
	bool got = checkAuth(request, result);
	int ret = got ? 0 : 401;
	return ret;
}

void RestHttpHandler::setCheckExpired(HttpServer* server, int seconds)
{
	auto timerTask = [=]()
	{
		m_auth->checkExpired();
	};
	server->setTimerTask(seconds, timerTask);
}

void RestHttpHandler::checkExpired()
{
	m_auth->checkExpired();
}